/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package server;

import data.Card;
import data.InitialData;
import data.NumberCard;
import data.Vote;
import data.VotingData;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import javax.xml.bind.JAXBException;
import javax.xml.stream.XMLStreamException;
import utils.XMLParser;
import models.PersonalData;
import java.io.StringReader;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.DatatypeConverter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import manager.ServerTool;
import models.Candidate;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.ssl.SslFilter;
import org.jdom.*;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import utils.CleanUp;
import utils.SignCertificate;
import utils.SplitMessage;

/**
 *
 * @author Pawel
 */
public class VotingProtocolHandler extends IoHandlerAdapter {

    private ServerTool serverTool;
    private InitialData initialData;
    public static Semaphore semaphore = new Semaphore(1, true);

    public VotingProtocolHandler() {
        serverTool = new ServerTool();
        initialData = new InitialData();
        initialData.getCandidates().addAll(getCandidates());
        initialData.setE(serverTool.getE());
        initialData.setN(serverTool.getN());
        initialData.setNumberOfCard(10);
    }

    @Override
    public void sessionOpened(IoSession session) {
        session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 20);
        //session.setAttribute(SslFilter.USE_NOTIFICATION);

        System.out.println("2: 0000 " + session.getReadBytes());
        SslFilter fltr = (SslFilter) session.getFilterChain().get("sslFilter");
        // można by pewnie sprawdzić podpis, ale chyba nie trzeba, skoro jest trusted
    }

    @Override
    public void exceptionCaught(IoSession session, Throwable cause) {
        cause.printStackTrace();
        session.close(true);
    }

    private String duzyString(int size) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; ++i) {
            sb.append(i);
        }
        return sb.toString();
    }

    @Override
    public void messageReceived(IoSession session, Object message) throws Exception {
        String s = ((IoBuffer) message).getString(Charset.forName("UTF-8").newDecoder()).trim();
        System.out.println("===");
        System.out.println(s);
        System.out.println("===");
        parseMessage(session, s);

    }

    private void parseMessage(IoSession session, String message) {
        SAXBuilder builder = new SAXBuilder();
        try {
            Document document = builder.build(new StringReader(message));
            Element root = document.getRootElement();
            if (root.getName().equals("session")) {
                String sessionName = root.getAttribute("name").getValue();
                Element msg = root.getChild("msg");
                Document response = null;
                if (sessionName.equalsIgnoreCase("registration")) {
                    response = handleRegistration(msg);

                } else if (sessionName.equalsIgnoreCase("cardgen")) {
                    //SslFilter fltr = (SslFilter) session.getFilterChain().get("sslFilter");
                    //System.out.println(fltr.getSslSession(session).getPeerCertificateChain()[0]);
                    response = handleCardGeneration(msg, session);
                } else if (sessionName.equalsIgnoreCase("vote")) {
                    response = handleVoting(msg);
                } else {
                    throw new JDOMException("Wrong session name");
                }


                XMLOutputter out = new XMLOutputter();
                out.setFormat(Format.getPrettyFormat());
                IoBuffer buf = IoBuffer.allocate(out.outputString(response).length() * 8 + 200, true);

                buf.setAutoExpand(true);
                buf.setAutoShrink(true);
                buf.putString(out.outputString(response), Charset.forName("UTF-8").newEncoder());

                buf.flip();
                WriteFuture wf = session.write(buf);
            } else {
                throw new JDOMException("No session given");
            }
        } catch (JDOMException e) {
            System.out.println("Malformed XML");
            System.out.println(e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    private Document handleRegistration(Element message) {
        Element session = new Element("session");
        session.setAttribute("name", "registration");
        Document reDoc = new Document(session);
        Element content = message.getChild("PersonalData");
        PersonalData np = new PersonalData();
        System.out.println("PersonalData");
        try {
            XMLParser.parseObjectFromXml(content, np);
        } catch (IllegalArgumentException ex) {
            Logger.getLogger(VotingProtocolHandler.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(VotingProtocolHandler.class.getName()).log(Level.SEVERE, null, ex);
        }

        Element sigrec = message.getChild("PKCS10");
        try {
            semaphore.acquire();
        } catch (InterruptedException ex) {
            Logger.getLogger(VotingProtocolHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
        int ret = SignCertificate.dealWithSignReq(sigrec, np, "bskbsk");
        semaphore.release();

        // tu trzeba sprawdzić poprawnosc danych w bazie

        Element msg = new Element("msg");
        Element signed_cert = new Element("signedCertificate");
        Element result = new Element("result");
        msg.addContent(result);
        msg.addContent(signed_cert);
        Element cert = new Element("Certificate");
        signed_cert.addContent(cert);
        System.out.println("ret == " + ret);


        if (ret == 0) {
            byte[] bytes;

            try {
                bytes = loadSignedCert(np);

                //     System.out.println(bytesToString(bytes));
                result.addContent("OK");
                //System.out.println("Takie byte wczytałem : " + bytesToString(bytes));
                SplitMessage sm = new SplitMessage();
                cert = sm.splitContent(cert, bytesToString(bytes), 200);




            } catch (IOException ex) {
                Logger.getLogger(VotingProtocolHandler.class.getName()).log(Level.SEVERE, null, ex);

            }
        } else {
            result.addContent("FAIL"); // mozna dodac informacje tutaj
        }

        //    msg.addContent(new_content);
        session.addContent(msg);
        CleanUp.removePersonalData(np); // tu powinno usunąć , stworzyłem dwa plik .csr , i .pem
        XMLOutputter outputter = new XMLOutputter();
        outputter.setFormat(Format.getPrettyFormat());

        try {
            outputter.output(msg, System.out);
        } catch (IOException e) {
            System.err.println(e);
        }

        return reDoc;
    }

    private Document handleCardGeneration(Element msg, IoSession session) throws Exception {
        Element reSession = new Element("session");
        reSession.setAttribute("name", "cardgen");
        Document reDoc = new Document(reSession);
        Element reMsg = new Element("msg");
        reSession.addContent(reMsg);

        if (msg.getText().equals("init")) {
            serverTool = new ServerTool();
            initialData = new InitialData();
            initialData.setE(serverTool.getE());
            initialData.setN(serverTool.getN());
            initialData.setNumberOfCard(10);


            // Keep it simple!
            Element initData = new Element("initialData");
            Element e = new Element("e");
            e.addContent(initialData.getE().toString());
            Element n = new Element("n");
            n.addContent(initialData.getN().toString());
            Element numOfCard = new Element("numberOfCard");
            numOfCard.addContent(initialData.getNumberOfCard().toString());
            initData.addContent(e);
            initData.addContent(n);
            initData.addContent(numOfCard);
            // Nazwiska kandydatów - do pobrania z bazy danych
            LinkedList<Candidate> candidateList = getCandidates();
            Element candidates = new Element("Candidates");
            for (Candidate c : candidateList) {
                Element can = new Element("Candidate");
                can.setAttribute("id", new Integer(c.getId()).toString());
                can.setAttribute("name", c.getName());
                candidates.addContent(can);
            }
            reMsg.addContent(candidates);
            reMsg.addContent(initData);



        } else if ((msg.getChild("initialData")) != null) {
            votingProcess(session, msg.getChildText("initialData"));



        } else if ((msg.getChild("votingData")) != null) {
            System.out.println("serwer votingdata");
            
            List<Element> cards = msg.getChild("votingData").getChildren("Card");
            VotingData vdata = new VotingData();
            vdata.setsId(Integer.parseInt(msg.getChild("votingData").getChild("sId").getValue()));
            vdata.setName(msg.getChild("votingData").getChild("name").getValue());
            for (Element c : cards) {
                Card card = new Card();
                card.setNumber(c.getAttribute("number").getIntValue());
                for (Element v : (List<Element>)c.getChild("votes").getChildren("blindVote")) {
                    Vote newVote = new Vote();
                    newVote.setBlindVote(new BigInteger(v.getValue()));
                    card.getVotes().add(newVote);
                }
                vdata.getCards().add(card);
            }

            System.out.println("serwer votingdata 1");
            System.out.println("voter: " + vdata.getsId());
            serverTool.addVotingData(vdata);
            Integer number = serverTool.randomCard(vdata.getName());
            System.out.println("ok");
            serverTool.getVotingData(vdata.getName()).skipCard(number);
            System.out.println("Send number card");
            Element cNum = new Element("cardNumber");
            reMsg.addContent(cNum);
            cNum.addContent(number.toString());

            session.setAttribute("vdata", vdata);



        } else if (msg.getValue().equalsIgnoreCase("unblinding data")) {
            VotingData voter = (VotingData) session.getAttribute("vdata");
            for(Card c : voter.getCards()) {
                for(Element crd : (List<Element>)msg.getParentElement().getChild("unblindData")
                        .getChildren("card")) {
                    if(c.getNumber() == crd.getAttribute("number").getIntValue()) {
                        c.setR(new BigInteger(crd.getAttribute("r").getValue()));
                    }
                }
            }
            //VotingData vdata = serverTool.getVotingData(voter.getName());
            VotingData vdata = (VotingData)session.getAttribute("vdata");
            Boolean valid = serverTool.verifyBlind(voter, voter.getName());//dopisac co w przeciwnym przypadku
            System.out.println(valid);
            Card signCard = serverTool.signCard(vdata.getSkipCard());
            //Element re = sendSignCard(signCard, session);
            //vdata.setStep(2);
            Element signedCard = new Element("signedCard");
            System.out.println(signCard.getVotes().size());
            for(Vote v : signCard.getVotes()) {
                Element bsv = new Element("blindSignedVote");
                bsv.addContent(v.getBlindSignVote().toString());
                signedCard.addContent(bsv);
            }
            signedCard.setAttribute("number", signCard.getNumber().toString());
            reMsg.addContent(signedCard);
        }
        return reDoc;
    }

    private Document handleVoting(Element msg) {
        return null;
    }

    private byte[] loadSignedCert(PersonalData np) throws IOException {

        StringBuilder sb = new StringBuilder();
        sb.append("certs").append(File.separatorChar).append(np.firstName).append(np.lastName).append(np.Pesel).append(".pem");
        System.out.println(sb.toString());
        return readBytes(new File(sb.toString()));
    }

    private byte[] readBytes(File file) throws IOException {
        FileInputStream is = new FileInputStream(file);

        // Get the size of the file
        long length = file.length();

        // Create the byte array to hold the data
        byte[] bytes = new byte[(int) length];

        // Read in the bytes
        int offset = 0;
        int numRead = 0;
        while (offset < bytes.length
                && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
            offset += numRead;
        }

        // Ensure all the bytes have been read in
        if (offset < bytes.length) {
            throw new IOException("Could not completely read file " + file.getName());
        }

        // Close the input stream and return bytes
        is.close();
        return bytes;
    }

    private String bytesToString(byte[] bytes) {
        return DatatypeConverter.printBase64Binary(bytes);
    }

    private byte[] stringToBytes(String string) {
        return DatatypeConverter.parseBase64Binary(string);
    }

    private LinkedList<Candidate> getCandidates() {
        LinkedList<Candidate> list = new LinkedList<Candidate>();
        list.add(new Candidate(1, "Jan Kowalski"));
        list.add(new Candidate(2, "Juan Carlos"));
        list.add(new Candidate(3, "John Smith"));
        return list;
    }

    private void votingProcess(IoSession session, String s) throws XMLStreamException, JAXBException, UnsupportedEncodingException, Exception {
        ByteArrayInputStream in = new ByteArrayInputStream(s.getBytes());
        XMLStreamReader xsr = XMLInputFactory.newInstance().createXMLStreamReader(in);
        xsr.nextTag();
        System.out.println("TagName:" + xsr.getLocalName());


        if ("initialData".equalsIgnoreCase(xsr.getLocalName())) {


            JAXBContext jc = JAXBContext.newInstance(InitialData.class);
            Unmarshaller u = jc.createUnmarshaller();

            xsr.require(XMLStreamReader.START_ELEMENT,
                    null, "initialData");
            InitialData data = (InitialData) u.unmarshal(xsr);

            System.out.println(
                    "initial data");

            IoBuffer buf = IoBuffer.allocate(16899, true);

            buf.setAutoExpand(
                    true);
            buf.setAutoShrink(
                    true);
//                buf.putString(out.outputString(response), Charset.forName("UTF-8").newEncoder());
            JAXBContext context;
            context = JAXBContext.newInstance(InitialData.class);

            //XMLOutputter out = new XMLOutputter();
            //XMLStreamWriter xmlStreamWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(out);
            System.out.println(
                    "Sendinitial data");
            Marshaller m = context.createMarshaller();

            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
                    true);
            m.setProperty(Marshaller.JAXB_ENCODING,
                    "UTF-8");
            m.marshal(initialData, buf.asOutputStream());
            buf.flip();
            WriteFuture wf = session.write(buf);
        } else if ("votingData".equalsIgnoreCase(xsr.getLocalName())) {
            System.out.println("serwer votingdata");
            JAXBContext jc = JAXBContext.newInstance(VotingData.class);
            Unmarshaller u = jc.createUnmarshaller();

            xsr.require(XMLStreamReader.START_ELEMENT,
                    null, "votingData");
            VotingData voter = (VotingData) u.unmarshal(xsr);


            if (voter.getStep()
                    == 0) {

                System.out.println("serwer votingdata 1");
                System.out.println("voter: " + voter.getsId());
                serverTool.addVotingData(voter);
                Integer number = serverTool.randomCard(voter.getName());
                System.out.println("ok");
                voter.skipCard(number);
                sendNumber(number, session);
                System.out.println("ok");
                voter.setStep(1);
            } else if (voter.getStep()
                    == 1) {
                System.out.println("serwer votingdata 2");
                System.out.println("voter: " + voter.getsId());
                VotingData vdata = serverTool.getVotingData(voter.getName());
                Boolean valid = serverTool.verifyBlind(voter, voter.getName());//dopisac co w przeciwnym przypadku
                Card signCard = serverTool.signCard(voter.getName());
                sendSignCard(signCard, session);
                vdata.setStep(2);

            }
        } else {
            System.out.println("Nothing to bind!");
        }
    }
//
//    private void parseVotingDataWithR(IoSession session, String s) throws XMLStreamException, JAXBException, UnsupportedEncodingException {
//        //System.out.println("zmienna R :  " + serverTool.getStep()+"    "+ s);
//
//        ByteArrayInputStream in = new ByteArrayInputStream(s.getBytes());
//        XMLStreamReader xsr = XMLInputFactory.newInstance().createXMLStreamReader(in);
//        xsr.nextTag();
//        System.out.println("TagName:" + xsr.getLocalName());
//
//        if ("votingData".equalsIgnoreCase(xsr.getLocalName())) {
//            JAXBContext jc = JAXBContext.newInstance(VotingData.class);
//            Unmarshaller u = jc.createUnmarshaller();
//            xsr.require(XMLStreamReader.START_ELEMENT, null, "votingData");
//            VotingData voter = (VotingData) u.unmarshal(xsr);
//            System.out.println("voter: " + voter.getsId());
//            serverTool.setClientVotingData(voter);
//            Boolean valid = serverTool.verifyBlind(voter);//dopisac co w przeciwnym przypadku
//            Card signCard = serverTool.signCard();
//            sendSignCard(signCard, session);
//            serverTool.setStep(2);
//        } //        else if ("initialData".equalsIgnoreCase(xsr.getLocalName())) {
//        //            JAXBContext jc = JAXBContext.newInstance(InitialData.class);
//        //            Unmarshaller u = jc.createUnmarshaller();
//        //            xsr.require(XMLStreamReader.START_ELEMENT, null, "initialData");
//        //            InitialData data = (InitialData) u.unmarshal(xsr);
//        //            System.out.println("initial data");
//        //
//        //
//        //        } 
//        else {
//            System.out.println("Nothing to bind!");
//        }
//    }

    private void sendNumber(Integer num, IoSession session) throws JAXBException {
        System.out.println("sendNumber()");
        IoBuffer buf = IoBuffer.allocate(16899, true);
        buf.setAutoExpand(true);
        buf.setAutoShrink(true);
//                buf.putString(out.outputString(response), Charset.forName("UTF-8").newEncoder());
        JAXBContext context;
        context = JAXBContext.newInstance(NumberCard.class);


        //XMLOutputter out = new XMLOutputter();
        //XMLStreamWriter xmlStreamWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(out);
        System.out.println(
                "Send number card");
        Marshaller m = context.createMarshaller();

        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
                true);
        m.setProperty(Marshaller.JAXB_ENCODING,
                "UTF-8");
        m.marshal(
                new NumberCard(num), buf.asOutputStream());
        buf.flip();
        WriteFuture wf = session.write(buf);
    }

    private Element sendSignCard(Card signCard, IoSession session) throws JAXBException {
        IoBuffer buf = IoBuffer.allocate(16899, true);
        buf.setAutoExpand(true);
        buf.setAutoShrink(true);
        JAXBContext context;
        context = JAXBContext.newInstance(Card.class);
        System.out.println(
                "Send signed card");
        Marshaller m = context.createMarshaller();

        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
                true);
        m.setProperty(Marshaller.JAXB_ENCODING,
                "UTF-8");
        m.marshal(signCard, buf.asOutputStream());
        buf.flip();
        WriteFuture wf = session.write(buf);
        return null;
    }
}